package com.xuxueli.applyModules.utils;
import com.xuxueli.applyModules.entity.DownloadZipDto;
import com.xuxueli.applyModules.entity.FileInfo;
import com.xuxueli.applyModules.exception.ErrorException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import static org.springframework.util.StreamUtils.BUFFER_SIZE;

/**
 * 附件下载  生成压缩包
 */
@Slf4j
public class ZipUtils {

    /**
     * 多个文件压缩下载
     * @param outputStream
     * @param fileList 多个file文件list
     * @throws IOException
     */
    public static void downloadZip(OutputStream outputStream, List<FileInfo> fileList) throws IOException {
        try (ZipOutputStream zos = new ZipOutputStream(outputStream)) {
            //zos.setEncoding("GBK");
            for (FileInfo fileInfo : fileList) {
                File file = new File(fileInfo.getFilePath());
                if (file.exists()) {
                    ZipEntry zipEntry = new ZipEntry(fileInfo.getFileName());
                    zos.putNextEntry(zipEntry);
                    byte[] buf = new byte[BUFFER_SIZE];
                    int len;
                    FileInputStream in = new FileInputStream(file);
                    while ((len = in.read(buf)) != -1) {
                        zos.write(buf, 0, len);
                        zos.flush();
                    }
                }
            }
            zos.flush();
        } catch (IOException e) {
            log.error("文件压缩异常：", e);
            throw e;
        } finally {
            try {
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException e) {
                log.error("导出文件关闭流异常", e);
            }
        }
    }

    /**
     * 压缩的文件就是传递过来的文件名称
     * @param outputStream
     * @param fileList
     * @throws IOException
     */
    public static void downloadZipOldName(OutputStream outputStream, List<File> fileList) throws IOException {
        try (ZipOutputStream zos = new ZipOutputStream(outputStream)) {
            //zos.setEncoding("GBK");
            for (File file : fileList) {
                if (file.exists()) {
                    ZipEntry zipEntry = new ZipEntry(file.getName());
                    zos.putNextEntry(zipEntry);
                    byte[] buf = new byte[BUFFER_SIZE];
                    int len;
                    FileInputStream in = new FileInputStream(file);
                    while ((len = in.read(buf)) != -1) {
                        zos.write(buf, 0, len);
                        zos.flush();
                    }
                }
            }
            zos.flush();
        } catch (IOException e) {
            log.error("文件压缩异常：", e);
            throw e;
        } finally {
            try {
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException e) {
                log.error("导出文件关闭流异常", e);
            }
        }
    }

    /**
     * 将文件夹及文件夹包含的内容压缩成zip文件
     * (为了解决中文乱码的问题，ZipOutputStream用org.apache.tools.zip.*)
     *
     * @param inputFile 源文件
     * @param delFlag   删除源文件标记
     * @return File 压缩后的文件
     */
    public static File zipCompress(File inputFile, boolean delFlag) throws Exception {
        File zipFile = null;
        //创建zip输出流
        //为了解决中文乱码的问题,ZipOutputStream用org.apache.tools.zip.*
        //不要用 java.util.zip.*
        if (inputFile != null && inputFile.exists()) {
            try {
                String path = inputFile.getCanonicalPath();
                String zipFileName = path + ".zip";
                zipFile = new File(zipFileName);
                if (zipFile.exists()) {
                    zipFile.delete();
                }
                zipFile.createNewFile();//创建文件
                try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))){
                    //解决中文乱码问题,指定编码GBK
                    //zos.setEncoding("GBK");
                    //压缩文件或文件夹
                    compressFile(zos, inputFile, inputFile.getName());
                }
            } catch (Exception e) {
                log.error("文件压缩异常：", e);
                throw e;
            } finally {
                if (delFlag) {
                    //递归删除源文件及源文件夹
                    deleteFile(inputFile);
                }
            }
        }
        return zipFile;
    }

    /**
     * 压缩文件或文件夹
     * (ZipEntry 使用org.apache.tools.zip.*，不要用 java.util.zip.*)
     *
     * @param zos        zip输出流
     * @param sourceFile 源文件
     * @param baseName   父路径
     * @throws Exception 异常
     */
    private static void compressFile(ZipOutputStream zos, File sourceFile, String baseName) throws Exception {
        if (!sourceFile.exists()) {
            return;
        }

        //若路径为目录（文件夹）
        if (sourceFile.isDirectory()) {
            //取出文件夹中的文件（或子文件夹）
            File[] fileList = sourceFile.listFiles();
            //若文件夹为空，则创建一个目录进入点
            if (fileList.length == 0) {
                //文件名称后跟File.separator表示这是一个文件夹
                zos.putNextEntry(new ZipEntry(baseName + File.separator));
                //若文件夹非空，则递归调用compressFile,对文件夹中的每个文件或每个文件夹进行压缩
            } else {
                for (int i = 0; i < fileList.length; i++) {
                    compressFile(zos, fileList[i],
                            baseName + File.separator + fileList[i].getName());
                }
            }

            //若为文件,则先创建目录进入点,再将文件写入zip文件中
        } else {
            ZipEntry ze = new ZipEntry(baseName);
            //设置ZipEntry的最后修改时间为源文件的最后修改时间
            ze.setTime(sourceFile.lastModified());
            zos.putNextEntry(ze);

            try (FileInputStream fis = new FileInputStream(sourceFile)) {
                copyStream(fis, zos);
            }
        }
    }

    /**
     * 流拷贝
     *
     * @param in  输入流
     * @param out 输出流
     * @throws IOException
     */
    private static void copyStream(InputStream in, OutputStream out) throws IOException {
        int bufferLength = 1024 * 100;
        synchronized (in) {
            synchronized (out) {
                int count = 0;
                byte[] buffer = new byte[bufferLength];
                while ((count = in.read(buffer, 0, bufferLength)) != -1) {
                    out.write(buffer, 0, count);
                }
                out.flush();
            }
        }
    }

    /**
     * 递归删除文件夹中的目录及文件
     *
     * @param sourceFile
     * @throws Exception
     */
    private static void deleteFile(File sourceFile) throws Exception {
        //如果路径为目录
        if (sourceFile.isDirectory()) {
            //取出文件夹中的文件或子文件夹
            File[] fList = sourceFile.listFiles();
            if (fList.length == 0) {
                sourceFile.delete();
            } else {
                for (int i = 0; i < fList.length; i++) {
                    deleteFile(fList[i]);
                }
                sourceFile.delete();
            }
            //如果为文件则直接删除
        } else {
            sourceFile.delete();
        }
    }

    /**
     * 下载压缩包
     *
     * @param downloadZipDto
     * @param response
     * @throws IOException
     */
    public void downloadZipFromUrl(DownloadZipDto downloadZipDto, HttpServletResponse response) {
        // 创建临时路径,存放压缩文件
        String zipFilePath = "d:\\test" + "\\11.zip";
        try (ServletOutputStream outputStream = response.getOutputStream()){
            // 压缩输出流,包装流,将临时文件输出流包装成压缩流,将所有文件输出到这里,打成zip包
            ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFilePath));
            // 循环调用压缩文件方法,将一个一个需要下载的文件打入压缩文件包
            List<String> urls = downloadZipDto.getUrls();
            for (String url : urls) {
                int lastIndexOf = url.lastIndexOf('/');
                String name = url.substring(lastIndexOf + 1);
                String newPath = "d:\\test" + "\\" + name;
                // 该方法在下面定义
                fileToZip(newPath, zipOut);
            }
            zipOut.flush();
            // 压缩完成后,关闭压缩流
            zipOut.close();
            response.setHeader("Content-Disposition", "attchment;filename="+"1.zip");
            //该流不可以手动关闭,手动关闭下载会出问题,下载完成后会自动关闭
            FileInputStream inputStream = new FileInputStream(zipFilePath);
            // 如果是SpringBoot框架,在这个路径
            // org.apache.tomcat.util.http.fileupload.IOUtils产品
            // 否则需要自主引入apache的 commons-io依赖
            // copy方法为文件复制,在这里直接实现了下载效果
            IOUtils.copy(inputStream, outputStream);
            outputStream.close();
            // 关闭输入流
            inputStream.close();
            //下载完成之后，删掉这个zip包
            File fileTempZip = new File(zipFilePath);
            fileTempZip.delete();
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    private void fileToZip(String filePath, ZipOutputStream zipOut) {
        // 需要压缩的文件
        File file = new File(filePath);
        // 获取文件名称,如果有特殊命名需求,可以将参数列表拓展,传fileName
        String fileName = file.getName();
        FileInputStream fileInput = null;
        BufferedInputStream bufferStream = null;
        try {
            fileInput = new FileInputStream(filePath);
            // 缓冲
            byte[] bufferArea = new byte[1024];
            bufferStream = new BufferedInputStream(fileInput);

            // 将当前文件作为一个zip实体写入压缩流,fileName代表压缩文件中的文件名称
            zipOut.putNextEntry(new ZipEntry(fileName));
            int length = 0;
            // 最常规IO操作,不必紧张
            while ((length = bufferStream.read(bufferArea)) != -1) {
                zipOut.write(bufferArea, 0, length);
            }
            // 解决剩余的
            int remain = bufferStream.available();
            byte[] last = new byte[remain];
            bufferStream.read(last);
            zipOut.write(last);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                //关闭流
                fileInput.close();
                // 需要注意的是缓冲流必须要关闭流,否则输出无效
                bufferStream.close();
                // 压缩流不必关闭,使用完后再关
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static String toZip(List<String> srcFiles, Map<String, String> fileNameMap) throws RuntimeException {
        long start = System.currentTimeMillis();
        String fileName = "D:\\data\\" + UUID.randomUUID().toString() + ".zip";
        ZipOutputStream zos = null;
        try {
            FileOutputStream out = new FileOutputStream(fileName);
            zos = new ZipOutputStream(out);
            for (String srcFile : srcFiles) {
                byte[] buf = new byte[4096];
                zos.putNextEntry(new ZipEntry(fileNameMap.get(srcFile)));
                int len;
                FileInputStream in = new FileInputStream(srcFile);
                while ((len = in.read(buf)) != -1) {
                    zos.write(buf, 0, len);
                }
                in.close();
            }
            zos.setComment("导出文件");
            zos.flush();
            zos.closeEntry();
            long end = System.currentTimeMillis();
            log.info("导出文件打包完成，耗时：" + (end - start) + " ms");
        } catch (Exception e) {
            throw new ErrorException(400, "打包文件时出错:"+e.toString());
        } finally {
            if (zos != null) {
                try {
                    zos.close();
                } catch (IOException e) {
                    log.error("打包文件时出错:", e);
                }
            }
        }
        return fileName;
    }


    public static void main(String[] args)  throws IOException {
        List list = new ArrayList();
        list.add("D:\\data\\"+"aaa.docx");
        list.add("D:\\data\\"+"day.docx");

        list.add("D:\\data\\"+"2022-09-07.log");
        list.add("D:\\data\\"+"test.txt");
        Map<String, String> fileNameMap = new HashMap<>();
        fileNameMap.put("D:\\data\\"+"aaa.docx","aaa.docx");
        fileNameMap.put("D:\\data\\"+"day.docx","day.docx");
        fileNameMap.put("D:\\data\\"+"2022-09-07.log","2022-09-07.log");
        fileNameMap.put("D:\\data\\"+"test.txt","test.txt");
        toZip(list,fileNameMap);

        //出现内容乱码
//        //定义压缩文件夹的名称和相关的位置
//        File zipFile = new File("D:\\data\\" + "country.zip");
//        log.info(""+zipFile);
//        InputStream input = null;
//        //定义压缩输出流
//        ZipOutputStream zipOut = null;
//        //实例化压缩输出流  并定制压缩文件的输出路径  就是D盘下【D:\新建文件夹\country.zip】的位置处
//        zipOut = new ZipOutputStream(new FileOutputStream(zipFile), StandardCharsets.ISO_8859_1);
//        for (Object o : list) {
//            File file = new File((String) o);
//
//            //定义输入文件流
//            input = new FileInputStream(file);
//            zipOut.putNextEntry(new ZipEntry(file.getName()));
//            //设置注释
//            zipOut.setComment("www.demo.com");
//            int temp = 0;
//            int readsize = 1024*1024*5;
//            byte [] buf = new byte[readsize];
//            while ((temp = input.read(buf))!=-1){
//                zipOut.write(temp);
//            }
//            input.close();
//        }
//        zipOut.close();
    }


}